/**
 * 
 */
package controller;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Semaphore;

import model.EErrorPopUp;
import model.preprocessor.EPreProcessor;
import model.preprocessor.PreProcessorFactory;
import model.session.CNFUse;
import model.session.CNFUseStatus;
import model.session.ExecutableSessionBlock;
import model.session.ExecutionStatus;
import model.session.IExecutionListener;
import model.session.PreprocessorExecutionBlock;
import model.session.Session;
import model.session.SessionBlock;
import model.session.SolverExecutionBlock;
import model.solver.ESolver;
import model.solver.ImportESolver;
import model.solver.SolverFactory;
import vue.IIGSessionManager;
import controller.preprocessor.PreProcessor;
import controller.solver.Solver;

/**
 * Implementation of {@link IExtendedSessionManager}.
 * 
 * @author GRONOWSKI Stephane svan001@aol.com
 * 
 *         igsat 2010
 * 
 */
public class SessionManager implements IExtendedSessionManager, IExecutionListener {
	/** Error message display when a error relative to the semaphore occur */
	public static final String						ERROR_ACQUIRE	= "Error when acquire semaphore : \n details : ";

	private IIGSessionManager						ig				= null;
	private Session									session;
	private Map<PreprocessorExecutionBlock, CNFUse>	preprocExec		= new HashMap<PreprocessorExecutionBlock, CNFUse>();
	private Map<SolverExecutionBlock, CNFUse>		solverExec		= new HashMap<SolverExecutionBlock, CNFUse>();
	private SolverFactory							solverFactory;
	private PreProcessorFactory						preprocFactory;
	private Semaphore								sem				= new Semaphore(1, true);

	/**
	 * Create a session manager.
	 * 
	 * @param cnf
	 *            cnf that begin the session.
	 * @param solverFactory
	 *            a {@link SolverFactory}
	 * @param preprocFactory
	 *            a {@link PreProcessorFactory}
	 */
	public SessionManager(CNFUse cnf, SolverFactory solverFactory, PreProcessorFactory preprocFactory) {

		this.solverFactory = solverFactory;
		this.preprocFactory = preprocFactory;

		openSession(cnf);
		// ig
		// ig.beginSession(session.getFirst());
	}

	/**
	 * Create a session manager.
	 * 
	 * @param session
	 *            the session.
	 * @param solverFactory
	 *            a {@link SolverFactory}
	 * @param preprocFactory
	 *            a {@link PreProcessorFactory}
	 */
	public SessionManager(Session session, SolverFactory solverFactory, PreProcessorFactory preprocFactory) {

		this.solverFactory = solverFactory;
		this.preprocFactory = preprocFactory;

		this.session = session;
		// ig
		// ig.beginSession(session.getFirst());
	}

	/* creata a session */
	private void openSession(CNFUse cnf) {
		session = new Session(cnf);
	}

	@Override
	public boolean close() {
		boolean res = true;
		try
		{
			sem.acquire();
		} catch (InterruptedException e)
		{
			e.printStackTrace();
			ig.showErrorPopup(EErrorPopUp.FATAL_ERROR, ERROR_ACQUIRE + "error acquiring semaphore to close session");

			return false;
		}

		// arrete toutes les execution en cour
		for (PreprocessorExecutionBlock p : preprocExec.keySet())
			p.stopExecution();
		for (SolverExecutionBlock p : solverExec.keySet())
			p.stopExecution();

		// ferme la session / ttes les CNF
		if (!session.close())
			res = false;

		session = null;

		sem.release();

		return res;
	}

	@Override
	public void checkCNFStat(CNFUse cnf) {

		try
		{
			sem.acquire();
		} catch (InterruptedException e1)
		{
			e1.printStackTrace();
			ig.showErrorPopup(EErrorPopUp.FATAL_ERROR, ERROR_ACQUIRE + "error acquiring semaphore to check CNF stat");
			return;
		}

		try
		{
			cnf.getCnf().checkStat();
		} catch (IOException e)
		{
			e.printStackTrace();
			ig.showErrorPopup(EErrorPopUp.ERROR_FILE_STAT_CNF, cnf.getCnf().getCNFfile().getName());
		}
		finally
		{
			ig.updateCNFInformation(cnf);
			sem.release();
		}
	}

	@Override
	public void executePreProcessor(PreProcessor preproc, CNFUse cnf) {
		try
		{
			sem.acquire();
		} catch (InterruptedException e)
		{
			e.printStackTrace();
			ig.showErrorPopup(EErrorPopUp.FATAL_ERROR, ERROR_ACQUIRE + "error acquiring semaphore to execute preprocessor");
			return;
		}

		if (!session.containsCNFUse(cnf))
			throw new IllegalArgumentException();

		PreprocessorExecutionBlock block = new PreprocessorExecutionBlock(cnf.getFirst(), ExecutionStatus.READY, preproc, null);
		preprocExec.put(block, cnf);

		block.setPreviousExecutionTime(cnf.getPreviousExecutionTime());
		block.setListener(this);
		block.setCnf(cnf.getCnf());
		block.setSem(sem);
		Thread t = new Thread(block);
		t.start();

		try
		{// se bloque attendant le preproc soit pret/lancer
			sem.acquire();
			// ig
			ig.addRunningExecution(block, cnf);
		} catch (InterruptedException e)
		{
			e.printStackTrace();
			ig.showErrorPopup(EErrorPopUp.FATAL_ERROR, ERROR_ACQUIRE + "error acquiring semaphore to wait preprocessor preparation");
			return;
		}
		finally
		{
			sem.release();
			int i = 1 + 1;
			i++;

		}
	}

	@Override
	public void executeSolver(Solver solver, CNFUse cnf) {
		try
		{
			sem.acquire();
		} catch (InterruptedException e)
		{
			e.printStackTrace();
			ig.showErrorPopup(EErrorPopUp.FATAL_ERROR, ERROR_ACQUIRE + "error acquiring semaphore to execute solver");
			return;
		}

		if (!session.containsCNFUse(cnf))
			throw new IllegalArgumentException();

		SolverExecutionBlock block = new SolverExecutionBlock(cnf.getFirst(), ExecutionStatus.READY, solver, null);
		solverExec.put(block, cnf);

		block.setPreviousExecutionTime(cnf.getPreviousExecutionTime());
		block.setListener(this);
		block.setCnf(cnf.getCnf());
		block.setSem(sem);
		Thread t = new Thread(block);
		t.start();

		try
		{// se bloque attendant le solver soit pret/lancer
			sem.acquire();
			// ig
			ig.addRunningExecution(block, cnf);
		} catch (InterruptedException e)
		{
			e.printStackTrace();
			ig.showErrorPopup(EErrorPopUp.FATAL_ERROR, ERROR_ACQUIRE + "error acquiring semaphore to wait solver preparation");
			return;
		}
		finally
		{
			sem.release();
			int i = 1 + 1;
			i++;
		}

	}

	@Override
	public Set<EPreProcessor> getAvailablePreProcessor() {
		return preprocFactory.getAvailable();
	}

	@Override
	public Set<ESolver> getAvailableSolver() {
		return solverFactory.getAvailableSolver();
	}

	@Override
	public Set<ImportESolver> getAvailableImportSolver() {
		return solverFactory.getAvailableImport();
	}


	@Override
	public PreProcessorFactory getPreProcessorFactory() {
		return preprocFactory;
	}

	@Override
	public SolverFactory getSolverFactory() {
		return solverFactory;
	}


	@Override
	public void removeExecution(PreprocessorExecutionBlock exec) {
		try
		{
			sem.acquire();
		} catch (InterruptedException e)
		{
			e.printStackTrace();
			ig.showErrorPopup(EErrorPopUp.FATAL_ERROR, ERROR_ACQUIRE + "error acquiring semaphore to remove preprocessorExecutionBlock");
			return;
		}

		CNFUse use = session.containsPreprocessor(exec);
		if (use == null)
		{
			sem.release();
			return;
		}

		if (checkContainsRunningExecution(use.getPreproc().get(exec)))
		{
			ig.showErrorPopup(EErrorPopUp.ERROR_REMOVE_PREPROC, "Occur removing " + use);
			sem.release();
			return;
		}

		if (!session.closeCNFUse(use.removePreproc(exec)))
			ig.showErrorPopup(EErrorPopUp.ERROR_CLOSE_CNF, "");

		if (session.getFirst().getStatus() != CNFUseStatus.TO_BE_DETERMINED)
			session.majShortestPath();


		// ig
		ig.updateCNFUse(use);
		sem.release();
	}


	private boolean checkContainsRunningExecution(CNFUse cnf) {
		if (cnf == null)
			return false;

		if (preprocExec.values().contains(cnf) || solverExec.values().contains(cnf))
			return true;
		for (CNFUse tmp : cnf.getPreproc().values())
			if (checkContainsRunningExecution(tmp))
				return true;
		return false;
	}


	@Override
	public void removeExecution(SolverExecutionBlock exec) {
		try
		{
			sem.acquire();
		} catch (InterruptedException e)
		{
			e.printStackTrace();
			ig.showErrorPopup(EErrorPopUp.FATAL_ERROR, ERROR_ACQUIRE + "error acquiring semaphore to remove solverExecutionBlock");
			return;
		}
		CNFUse use = session.containsSolver(exec);
		if (use == null)
			return;

		use.removeSolver(exec);

		if (session.getFirst().getStatus() != CNFUseStatus.TO_BE_DETERMINED)
			session.majShortestPath();

		// ig
		ig.updateCNFUse(use);

		sem.release();
	}

	@Override
	public void reportEndExec(ExecutableSessionBlock exec) {
		System.out.println("fin d'exec");
		try
		{
			sem.acquire();
		} catch (InterruptedException e)
		{
			e.printStackTrace();
			ig.showErrorPopup(EErrorPopUp.FATAL_ERROR, ERROR_ACQUIRE + "error acquiring semaphore to report end of execution");
			return;
		}

		// retire de ce qui s'execute
		CNFUse mere = solverExec.remove(exec);
		if (mere != null)
		{
			System.out.println("fin execution solver");
			endExecSolver((SolverExecutionBlock) exec, mere);

			sem.release();
			return;
		}

		mere = preprocExec.remove(exec);
		if (mere != null)
		{
			System.out.println("fin execution preproc");
			endExecPreproc((PreprocessorExecutionBlock) exec, mere);

			sem.release();
			return;
		}

		sem.release();
		throw new IllegalArgumentException();
	}

	private void endExecPreproc(PreprocessorExecutionBlock exec, CNFUse mere) {
		if (mere == null)
			throw new IllegalArgumentException();

		// cree cnfuse du resultat
		CNFUse cnf = null;
		if (exec.getStatus() != ExecutionStatus.PROBLEM)
			switch (exec.getRes().getStatus()) {
			case SATISFIABLE:
				mere.setStatus(CNFUseStatus.PROVED_SAT);
				session.getFirst().setStatus(CNFUseStatus.PROVED_SAT);
				session.majShortestPath();
				break;
			case UNSATISFIABLE:
				mere.setStatus(CNFUseStatus.PROVED_UNSAT);
				session.getFirst().setStatus(CNFUseStatus.PROVED_UNSAT);
				session.majShortestPath();
				break;
			case OK:
				cnf = new CNFUse(session.getFirst(), exec.getRes().getCnf());
				cnf.setPreviousExecutionTime(exec.getTotalExecutionTime());
				if (mere.getCnf().isIrrelevant(cnf.getCnf()))
					cnf.setStatus(CNFUseStatus.IRRELEVANT);
				break;
			}

		// ajoute le preproc a la cnf retenue dou il vient
		mere.addPreproc(exec, cnf);

		// maj ig
		ig.removeRunningExecution(exec, mere);
		ig.updateCNFInformation(session.getFirst());
		ig.updateCNFUse(mere);
	}

	private void endExecSolver(SolverExecutionBlock exec, CNFUse mere) {
		if (mere == null)
			throw new IllegalArgumentException();

		// ajoute le preproc a la cnf retenue dou il vient
		mere.addSolver(exec);

		if (exec.getStatus() != ExecutionStatus.PROBLEM)
			switch (exec.getRes().getStatus()) {
			case SATISFIABLE:
				mere.setStatus(CNFUseStatus.PROVED_SAT);
				session.getFirst().setStatus(CNFUseStatus.PROVED_SAT);
				session.majShortestPath();
				break;
			case UNSATISFIABLE:
				mere.setStatus(CNFUseStatus.PROVED_UNSAT);
				session.getFirst().setStatus(CNFUseStatus.PROVED_UNSAT);
				session.majShortestPath();
				break;
			}

		// maj ig
		ig.removeRunningExecution(exec, mere);
		ig.updateCNFInformation(session.getFirst());
		ig.updateCNFUse(mere);
	}

	@Override
	public void setIIGSessionManager(IIGSessionManager manager) {
		try
		{
			sem.acquire();
		} catch (InterruptedException e)
		{
			e.printStackTrace();
			ig.showErrorPopup(EErrorPopUp.FATAL_ERROR, ERROR_ACQUIRE + "error acquiring semaphore to set IIGSessionMananger");
			return;
		}

		ig = manager;

		sem.release();
	}

	/**
	 * Return the set of preprocessor running at the moment with the {@link CNFUse} from where they
	 * have been lunched.
	 * 
	 * @return the set of preprocessor running at the moment with the {@link CNFUse} from where they
	 *         have been lunched.
	 */
	public Map<PreprocessorExecutionBlock, CNFUse> getPreprocExec() {
		return preprocExec;
	}

	/**
	 * Return the set of solver running at the moment with the {@link CNFUse} from where they have
	 * been lunched.
	 * 
	 * @return the set of solver running at the moment with the {@link CNFUse} from where they have
	 *         been lunched.
	 */
	public Map<SolverExecutionBlock, CNFUse> getSolverExec() {
		return solverExec;
	}

	@Override
	public void stopExecution(CNFUse use, ExecutableSessionBlock exec) {

		System.out.println("stop d'exec dans session manager");
		try
		{
			sem.acquire();
		} catch (InterruptedException e)
		{
			e.printStackTrace();
			ig.showErrorPopup(EErrorPopUp.FATAL_ERROR, ERROR_ACQUIRE + "error acquiring semaphore to report end of execution");
			return;
		}

		exec.stopExecution();

		sem.release();
	}

	@Override
	public void exportCNF(CNFUse cnf, String path) {
		System.out.println("export cnf");
		try
		{
			sem.acquire();
		} catch (InterruptedException e)
		{
			e.printStackTrace();
			ig.showErrorPopup(EErrorPopUp.FATAL_ERROR, ERROR_ACQUIRE + "error acquiring semaphore to export cnf");
			return;
		}

		File export = new File(path);
		if (export.exists() || cnf.getCnf().export(export.getName(), export.getParentFile()) == null)
		{
			ig.showErrorPopup(EErrorPopUp.ERROR_EXPORT_CNF, "");
			sem.release();
			return;
		}

		ig.updateCNFInformation(cnf);

		sem.release();
	}

	@Override
	public void exportXML(File parent, String directory) {
		try
		{
			sem.acquire();
		} catch (InterruptedException e)
		{
			e.printStackTrace();
			ig.showErrorPopup(EErrorPopUp.FATAL_ERROR, ERROR_ACQUIRE + "error acquiring semaphore to export cnf");
			return;
		}

		if (!preprocExec.isEmpty() || !solverExec.isEmpty())
		{// execution en cour
			ig.showErrorPopup(EErrorPopUp.ERROR_EXPORT_XML, "Please close all the current running execution before exporting the session");
			sem.release();
			return;
		}

		if (!parent.isDirectory() || new File(parent, directory).exists())
		{// verifie existance
			ig
					.showErrorPopup(EErrorPopUp.ERROR_EXPORT_XML,
							"Choose a directory to store the save, and a filename that is not already used");
			sem.release();
			return;
		}

		if (!session.exportXML(parent, directory))
		{// verifie existance
			ig.showErrorPopup(EErrorPopUp.ERROR_EXPORT_XML, "Error while exporting");
			sem.release();
			return;
		}

		sem.release();
	}

	@Override
	public List<SessionBlock> getShortestPath() {
		return session.getShortestPath();
	}

	@Override
	public void majSolver() {
		try
		{
			sem.acquire();
		} catch (InterruptedException e)
		{
			e.printStackTrace();
			ig.showErrorPopup(EErrorPopUp.FATAL_ERROR, ERROR_ACQUIRE + "error acquiring semaphore to set maj the solvers");
			return;
		}

		ig.updateCNFUse(session.getFirst());

		sem.release();
	}


	/*
	 * @Override public boolean containsCNFUse(CNFUse cnf) { try { sem.acquire(); } catch
	 * (InterruptedException e) { e.printStackTrace(); ig.showPopup(EPopUp.FATAL_ERROR,
	 * ERROR_ACQUIRE + "error acquiring semaphore to check if session contains CNFUse"); return
	 * false; }
	 * 
	 * boolean res = session.containsCNFUse(cnf);
	 * 
	 * sem.release();
	 * 
	 * return res; }
	 */

}
